package xyz.xtt.equity.utils;

import java.util.Collections;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

/**
 * @author dale
 * @since 2024/12/30
 **/
@Component
public class RedisLockUtil {
	// 释放锁Lua脚本：仅当锁的值匹配时才删除键,避免极端情况下删除其他线程添加的锁
	private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then " + "   return redis.call('del', KEYS[1]) " + "else "
			+ "   return 0 " + "end";
	// 资产交易记录订单锁key格式
	public static final String LOCK_KEY_USER_PURCHASE = "nft:lock:user:purchase:%s:%s";
	public static final String LOCK_KEY_USER_BENEFIT_PACKAGE_PURCHASE = "nft:lock:user:benefit:package:purchase:%d:%d";
	public static final String LOCK_KEY_PAY_ORDER = "nft:lock:pay:order:%d";
	public static final String LOCK_KEY_PAY_ORDER_UPDATE_ACCOUNT = "nft:lock:pay:order:update:account:%d";

	private final StringRedisTemplate redisTemplate;

	public RedisLockUtil(StringRedisTemplate redisTemplate) {
		this.redisTemplate = redisTemplate;
	}

	/**
	 * 尝试获取Redis分布式锁(在timeout时间内尝试多次)
	 *
	 * @param lockKey             锁key
	 * @param lockValue           锁value
	 * @param autoReleaseTime     自动释放时间
	 * @param autoReleaseTimeUnit 自动释放时间单位
	 * @param timeout             超时时间
	 * @param timeoutTimeUnit     超时时间单位
	 * @return
	 */
	public boolean tryLock(String lockKey, String lockValue, int autoReleaseTime, TimeUnit autoReleaseTimeUnit, int timeout, TimeUnit timeoutTimeUnit) {
		if (lockKey == null || lockValue == null || autoReleaseTime <= 0 || timeout <= 0) {
			throw new IllegalArgumentException("try lock failed.");
		}
		long endTime = System.currentTimeMillis() + timeoutTimeUnit.toMillis(timeout);
		while (System.currentTimeMillis() < endTime) {
			if (redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, autoReleaseTime, autoReleaseTimeUnit)) {
				return true;
			}
			try {
				Thread.sleep(ThreadLocalRandom.current().nextInt(50, 150));
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
				break;
			}
		}
		return false;
	}

	/**
	 * 获取Redis分布式锁
	 *
	 * @param lockKey             锁key
	 * @param lockValue           锁value
	 * @param autoReleaseTime     自动释放时间
	 * @param autoReleaseTimeUnit 自动释放时间单位
	 * @return 是否成功获取锁
	 */
	public boolean acquireLock(String lockKey, String lockValue, int autoReleaseTime, TimeUnit autoReleaseTimeUnit) {
		if (lockKey == null || lockValue == null || autoReleaseTime <= 0) {
			throw new IllegalArgumentException("try lock failed.");
		}
		return redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, autoReleaseTime, autoReleaseTimeUnit);
	}

	/**
	 * 使用 Lua 脚本释放 Redis 锁
	 *
	 * @param lockKey   锁的键
	 * @param lockValue 锁内容
	 * @return true 表示成功释放锁，false 表示释放锁失败
	 */
	public boolean releaseLock(String lockKey, String lockValue) {
		RedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_SCRIPT, Long.class);
		// 执行 Lua 脚本
		Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue);
		return result != null && result == 1;
	}

	/**
	 * 尝试获取Redis分布式锁
	 *
	 * @param lockKey             锁key
	 * @param lockValue           锁value
	 * @param autoReleaseTime     自动释放时间
	 * @param autoReleaseTimeUnit 自动释放时间单位
	 * @return
	 */
	public boolean tryLock(String lockKey, String lockValue, int autoReleaseTime, TimeUnit autoReleaseTimeUnit) {
		if (lockKey == null || lockValue == null || autoReleaseTime <= 0) {
			throw new IllegalArgumentException("try lock failed.");
		}
		return redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, autoReleaseTime, autoReleaseTimeUnit);
	}
}
